package com.hzw.saas.web.admin.hpc.controller;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.hzw.saas.api.hpc.IHpcAccountDetailService;
import com.hzw.saas.api.hpc.IHpcAccountRecordService;
import com.hzw.saas.api.hpc.IHpcAccountService;
import com.hzw.saas.api.hpc.IHpcAccountUserService;
import com.hzw.saas.api.hpc.bo.HpcAccountBo;
import com.hzw.saas.api.hpc.bo.HpcAccountRecordBo;
import com.hzw.saas.api.hpc.bo.HpcAccountUsageBo;
import com.hzw.saas.api.hpc.enums.DisplayUnit;
import com.hzw.saas.api.hpc.enums.HpcAccountStatus;
import com.hzw.saas.api.hpc.enums.HpcProviderType;
import com.hzw.saas.api.hpc.query.HpcAccountUserFrozenQuery;
import com.hzw.saas.api.hpc.query.HpcAccountUserUpdateQuery;
import com.hzw.saas.api.user.ICustomerService;
import com.hzw.saas.api.user.bo.CustomerBO;
import com.hzw.saas.common.config.annotation.SysLog;
import com.hzw.saas.common.config.exception.DefaultSaasException;
import com.hzw.saas.common.config.util.AssertUtil;
import com.hzw.saas.common.config.util.ExcelUtils;
import com.hzw.saas.common.config.util.PageParam;
import com.hzw.saas.common.util.Arith;
import com.hzw.saas.common.util.MapperUtil;
import com.hzw.saas.service.hpc.mapper.HpcAccountRecordMapper;
import com.hzw.saas.service.hpc.model.HpcAccountRecord;
import com.hzw.saas.web.admin.hpc.dto.HpcAccountCountInfoDto;
import com.hzw.saas.web.admin.hpc.dto.HpcAccountUsageDto;
import com.hzw.saas.web.admin.hpc.dto.HpcUsageTotalDTO;
import com.hzw.saas.web.admin.hpc.dto.MessageStatusInfoDTO;
import com.hzw.saas.web.admin.hpc.param.AddHpcCoreTimeParam;
import com.hzw.saas.web.admin.hpc.param.HpcAccountUserUpdateParam;
import com.hzw.saas.web.admin.hpc.param.HpcProviderUsageParam;
import io.swagger.annotations.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @author sonam
 * @sine 2021/11/26 12:41 下午
 */
@Slf4j
@Api(tags = "HPC/通用HPC账号")
@RestController
@RequiredArgsConstructor
@RequestMapping("/hpc/account")
public class HpcCommonController {

    private final HttpServletRequest request;
    private final HttpServletResponse response;

    private final ICustomerService customerService;
    private final IHpcAccountService hpcAccountService;
    private final IHpcAccountUserService hpcAccountUserService;
    private final IHpcAccountRecordService hpcAccountRecordService;
    private final HpcAccountRecordMapper recordMapper;
    private final IHpcAccountDetailService hpcAccountDetailService;

    @ApiOperationSort(1)
    @PostMapping("/frozen/{pid}")
    @SysLog(operation = "冻结/解冻用户的HPC账号", mode = 3)
    @ApiOperation(value = "冻结/解冻用户的HPC账号", notes = "指定需要冻结/解冻的用户的HPC账号")
    public ResponseEntity<Void> frozenHpcAccount(@ApiParam("HPC账号ID") @PathVariable String pid,
                                                 @ApiParam("1：解冻|0：冻结") @RequestParam("type") Integer type) {
        HpcAccountStatus hpcAccountStatus = HpcAccountStatus.instance(type);
        AssertUtil.assertThrow("操作失败，参数异常", hpcAccountStatus == null);
        hpcAccountUserService.frozenHpcAccount(new HpcAccountUserFrozenQuery()
            .setHpcAccountUserId(pid)
            .setHpcAccountStatus(hpcAccountStatus));
        return ResponseEntity.ok().build();
    }

    @ApiOperationSort(2)
    @GetMapping("/unbound/list")
    @ApiOperation(value = "未分配的HPC账号客户信息", notes = "未分配的HPC账号客户信息")
    public ResponseEntity<IPage<CustomerBO>> getUnBoundList(PageParam pageParam) {
        IPage<CustomerBO> customerBOIPage = customerService.listUnBoundCustomerList(pageParam);
        return ResponseEntity.ok(customerBOIPage);
    }

    @PutMapping("/renew")
    @ApiOperation(value = "补充HPC账号可用核时", notes = "记录给HPC账号补充的可用核时数量")
    @ApiOperationSort(3)
    @SysLog(operation = "补充HPC账号可用核时", mode = 3)
    public ResponseEntity<Double> addHpcCoreTime(@RequestBody @Validated AddHpcCoreTimeParam addHpcCoreTimeParam) {
        HpcAccountBo hpcAccountPoolQuery = new HpcAccountBo();
        hpcAccountPoolQuery.setHpcAccountId(addHpcCoreTimeParam.getHpcAccountId());
        List<HpcAccountBo> hpcAccountPoolBos = hpcAccountService.query(hpcAccountPoolQuery);
        AssertUtil.assertThrow("补充核时失败：当前HPC账号不存在", CollectionUtils.isEmpty(hpcAccountPoolBos));
        HpcAccountBo hpcAccountPoolBo = hpcAccountPoolBos.get(0);

        hpcAccountPoolBo.setCoreTimeAvailableTotal(Arith.add(addHpcCoreTimeParam.getCoreTime(), hpcAccountPoolBo.getCoreTimeAvailableTotal()));
        hpcAccountPoolBo.setCoreTimeAvailable(Arith.add(addHpcCoreTimeParam.getCoreTime(), hpcAccountPoolBo.getCoreTimeAvailable()));
        hpcAccountService.update(hpcAccountPoolBo);

        return ResponseEntity.ok(hpcAccountPoolBo.getCoreTimeAvailable());
    }

    @GetMapping("/count")
    @ApiOperation(value = "统计HPC账号数量", notes = "统计HPC账号总数、是否已分配数量、是否需要补充核时数量")
    @ApiOperationSort(4)
    public ResponseEntity<HpcAccountCountInfoDto> getHpcAccountCountInfoDto(@RequestParam(required = false) @ApiParam(value = "HPC账号类型,DQ01-并行,DN02-青云", defaultValue = "DQ01") String hpcType) {
        HpcProviderType hpcProviderType = HpcProviderType.instance(hpcType);

        int lacked = hpcAccountService.getNeedAddHpcCoreCount(hpcProviderType, null);
        int all = hpcAccountService.getHpcAccountSumCount(hpcProviderType);
        int unBound = hpcAccountService.getUsableHpcAccountCount(hpcProviderType);
        int frozen = hpcAccountUserService.getFreezeCount(hpcProviderType);
        return ResponseEntity.ok(new HpcAccountCountInfoDto()
            .setAll(all)  // 总数
            .setBound(all - unBound) // 已分配
            .setLacked(lacked) // 需要补充核时
            .setFrozen(frozen)   // 已冻结
            .setUnbound(unBound)); // 未分配
    }

    @ApiOperationSort(5)
    @PostMapping("/initialize")
    @SysLog(operation = "初始化用户的HPC数据")
    @ApiOperation(value = "初始化用户的HPC数据", notes = "指定HPC-USER-ID，初始化用户的HPC数据")
    public ResponseEntity<Void> hpcUserDateInitialize(@RequestBody(required = false) @ApiParam("HPC用户ID") List<String> hpcAccountUserIds) {
        if (CollectionUtil.isEmpty(hpcAccountUserIds)) {
            return ResponseEntity.ok().build();
        }
        hpcAccountUserIds.forEach(hpcAccountUserId -> hpcAccountUserService.initHpcUserData(hpcAccountUserId));
        return ResponseEntity.ok().build();
    }

    @PutMapping("/user")
    @ApiOperation(value = "更新并行HPC-USER账号", notes = "更新并行HPC-USER账号")
    @ApiOperationSort(6)
    @ApiResponses({
        @ApiResponse(code = 200, message = "更新成功")
    })
    @SysLog(operation = "更新并行HPC-USER账号", mode = 3)
    public ResponseEntity<Void> updateHpcAccountUser(@Validated @RequestBody HpcAccountUserUpdateParam hpcAccountUserUpdateParam) {
        hpcAccountUserService.update(MapperUtil.nf().map(hpcAccountUserUpdateParam, HpcAccountUserUpdateQuery.class));
        return ResponseEntity.ok().build();
    }

    @ApiOperationSort(7)
    @GetMapping("/status")
    @SysLog(operation = "查询HPC账号状态")
    @ApiOperation(value = "查询HPC账号状态", notes = "查询HPC账号状态")
    public ResponseEntity<MessageStatusInfoDTO> hpcAccountStatus(@RequestParam String hpcAccount) {
        MessageStatusInfoDTO statusInfoDTO = new MessageStatusInfoDTO();
        if (!ReUtil.isMatch("^[a-zA-Z]([a-z0-9]|[_!@-]){2,9}$", hpcAccount)) {
            statusInfoDTO.setType("error");
            statusInfoDTO.setMessage("账号长度为3~10，需字母开头，不能用大写字母和特殊符号");
        } else {
            HpcAccountBo account = hpcAccountService.getAccount(new HpcAccountBo().setHpcAccount(hpcAccount));
            if (Objects.isNull(account)) {
                statusInfoDTO.setType("success");
                statusInfoDTO.setMessage("该账号为全新账号，可放心使用");
            } else {
                // 查询hpcAccount，是否被其他用户占用
                HpcAccountRecordBo hpcAccountRecordBo = hpcAccountRecordService.searchOneByHpcAccountId(account.getHpcAccountId());
                if (Objects.isNull(hpcAccountRecordBo)) {
                    HpcAccountRecord hpcAccountRecord = recordMapper.getNearInvalidHpcRecord(account.getHpcAccountId());
                    if (Objects.isNull(hpcAccountRecord)) {
                        statusInfoDTO.setType("success");
                        statusInfoDTO.setMessage("该账号已创建，未被使用");
                    } else {
                        String userId = hpcAccountRecord.getUserId();
                        String customerName = getCustomerName(userId);
                        statusInfoDTO.setType("warn");
                        statusInfoDTO.setMessage(StrUtil.format("该账号曾经被用户'{}'使用，请确认是否已清理历史数据", customerName));
                    }
                } else {
                    String userId = hpcAccountRecordBo.getUserId();
                    statusInfoDTO.setType("error");
                    statusInfoDTO.setMessage(StrUtil.format("该HPC账号已存在，并且已绑定给用户'{}'，请重新输入", getCustomerName(userId)));
                }
            }
        }

        return ResponseEntity.ok(statusInfoDTO);
    }

    private String getCustomerName(String userId) {
        CustomerBO customer = customerService.getCustomerByUserId(userId);
        String customerName = "null";
        if (Objects.nonNull(customer)) {
            customerName = customer.getCustomerName();
        }
        return customerName;
    }

    @ApiOperationSort(8)
    @GetMapping("/user/count")
    @SysLog(operation = "查询用户绑定HPC账号个数")
    @ApiOperation(value = "查询用户绑定HPC账号个数", notes = "查询用户绑定HPC账号个数")
    public ResponseEntity<Integer> getUserHpcCount(String userId) {
        return ResponseEntity.ok(customerService.getCustomerHpcCount(userId));
    }

    @GetMapping("/usage/statistics/total")
    @SysLog
    @ApiOperation(value = "获取超算中心机时使用统计（总）", notes = "获取超算帐号机时使用统计总量")
    @ApiImplicitParams({
        @ApiImplicitParam(name = "hpcProviderType", value = "超算中心类型，DQ01-并行账号，DN02-青云账号")
    })
    @ApiOperationSort(6)
    public ResponseEntity<HpcUsageTotalDTO> getHpcAccountUsage(String hpcProviderType) {

        HpcProviderUsageParam param = new HpcProviderUsageParam();
        param.setHpcProviderType(hpcProviderType);
        List<String> recordIds = this.dealHpcUsageParam(param,false);
        HpcUsageTotalDTO totalUsage = new HpcUsageTotalDTO();
        Date end = new Date();
        DisplayUnit displayUnit = DisplayUnit.D;
        List<HpcAccountUsageBo> todayUsage = hpcAccountDetailService.listHpcAccountUsage(recordIds, DateUtil.beginOfDay(end), end, displayUnit);
        double todayUsageValue = todayUsage.stream().mapToDouble(HpcAccountUsageBo::getUsage).sum();
        totalUsage.setDay(NumberUtil.round(todayUsageValue,2).doubleValue());
        List<HpcAccountUsageBo> weekUsage = hpcAccountDetailService.listHpcAccountUsage(recordIds, DateUtil.beginOfWeek(end), end, displayUnit);
        double weekUsageValue = weekUsage.stream().mapToDouble(HpcAccountUsageBo::getUsage).sum();
        totalUsage.setWeek(NumberUtil.round(weekUsageValue,2).doubleValue());
        List<HpcAccountUsageBo> monthUsage = hpcAccountDetailService.listHpcAccountUsage(recordIds, DateUtil.beginOfMonth(end), end, displayUnit);
        double monthUsageValue = monthUsage.stream().mapToDouble(HpcAccountUsageBo::getUsage).sum();
        totalUsage.setMonth(NumberUtil.round(monthUsageValue,2).doubleValue());
        Double total = hpcAccountDetailService.getHpcAccountCoreTimeSums(recordIds);
        totalUsage.setTotal(NumberUtil.round(total, 2).doubleValue());
        return ResponseEntity.ok(totalUsage);
    }

    @GetMapping("/usage/statistics")
    @SysLog
    @ApiOperation(value = "获取超算中心机时使用统计（表）", notes = "获取超算帐号机时使用统计表单")
    @ApiOperationSort(6)
    public ResponseEntity<List<HpcAccountUsageDto>> listHpcAccountUsage(@Validated HpcProviderUsageParam hpcProviderUsageParam, @RequestParam(required = false) DisplayUnit displayUnit) {
        if (displayUnit == null) {
            displayUnit = DisplayUnit.D;
        }
        List<String> recordIds = dealHpcUsageParam(hpcProviderUsageParam, false);

        AssertUtil.assertThrow("查询最大不能超过1年", DateUtil.betweenDay(hpcProviderUsageParam.getStartDate(), hpcProviderUsageParam.getEndDate(), false) >= 366);
        List<HpcAccountUsageBo> hpcAccountUsageBos = hpcAccountDetailService.listHpcAccountUsage(recordIds, hpcProviderUsageParam.getStartDate(), hpcProviderUsageParam.getEndDate(), displayUnit);

        List<HpcAccountUsageDto> hpcAccountUsageDtoList = new ArrayList<>();
        DisplayUnit finalDisplayUnit = displayUnit;
        hpcAccountUsageBos.forEach(hpcAccountUsageBo -> {
            HpcAccountUsageDto hpcAccountUsageDto = MapperUtil.nf().map(hpcAccountUsageBo, HpcAccountUsageDto.class);
            Date date = hpcAccountUsageBo.getStartDate();
            hpcAccountUsageDto.setUsage(hpcAccountUsageDto.getUsage());
            switch (finalDisplayUnit) {
                case W:
                    hpcAccountUsageDto.setDate(StrUtil.format("{}年{}周", DateUtil.format(date, "yyyy"), DateUtil.weekOfYear(date)));
                    break;
                case M:
                    hpcAccountUsageDto.setDate(DateUtil.format(date, "yyyy年MM月"));
                    break;
                case H:
                    hpcAccountUsageDto.setDate(DateUtil.format(date, "yyyy年MM月dd日HH时"));
                    break;
                case D:
                default:
                    hpcAccountUsageDto.setDate(DateUtil.formatDate(date));
                    break;
            }
            hpcAccountUsageDtoList.add(hpcAccountUsageDto);
        });

        // 核时保留2位小数并且四舍五入
        hpcAccountUsageDtoList.forEach(hpcAccountUsageDto -> {
            hpcAccountUsageDto.setUsage(NumberUtil.round(hpcAccountUsageDto.getUsage(), 2).doubleValue());
        });

        // 按日期升序
        hpcAccountUsageDtoList.sort((a, b) -> DateUtil.compare(a.getStartDate(), b.getStartDate()));

        return ResponseEntity.ok(hpcAccountUsageDtoList);
    }

    @GetMapping("/usage/statistics/export")
    @SysLog
    @ApiOperation(value = "导出超算帐号机时使用统计", notes = "导出超算帐号机时使用统计")
    @ApiOperationSort(6)
    public void exportHpcAccountUsage(@Validated HpcProviderUsageParam hpcProviderUsageParam, @RequestParam(required = false) DisplayUnit displayUnit) {
        ResponseEntity<List<HpcAccountUsageDto>> result = this.listHpcAccountUsage(hpcProviderUsageParam, displayUnit);
        List<HpcAccountUsageDto> usageList = result.getBody();
//        Map<String, Double> usageMap = Objects.requireNonNull(usageList).stream()
//            .collect(Collectors.toMap(HpcAccountUsageDto::getDate, HpcAccountUsageDto::getUsage, (k1, k2) -> k1));
        try {
            log.debug("start export hpc usage excel...");
            String fileName = "hpc-usage";
            ExcelUtils.exportExcel(usageList, "算力使用统计", "算力使用统计", HpcAccountUsageDto.class, fileName, response);
        } catch (IOException e) {
            log.error("export error", e);
            throw DefaultSaasException.build("导出文件异常");
        }
    }

    /**
     * @param hpcProviderUsageParam
     * @param flag          是否去除当日
     * @return
     */
    private List<String> dealHpcUsageParam(@Validated HpcProviderUsageParam hpcProviderUsageParam, Boolean flag) {
        if (hpcProviderUsageParam.getOffsetDays() != null) {
            if (flag) {
                hpcProviderUsageParam.setEndDate(DateUtil.offsetDay(new Date(), -1));
            } else {
                hpcProviderUsageParam.setEndDate(new Date());
            }
            hpcProviderUsageParam.setStartDate(DateUtil.offsetDay(hpcProviderUsageParam.getEndDate(), -hpcProviderUsageParam.getOffsetDays()));
        } else {
            if (hpcProviderUsageParam.getEndDate() == null) {
                Date today = flag ? DateUtil.offsetDay(DateUtil.date(), -1) : DateUtil.date();
                hpcProviderUsageParam.setEndDate(today);
            }
            if (hpcProviderUsageParam.getStartDate() == null) {
                Integer offset = hpcProviderUsageParam.getOffsetDays();
                if (offset == null || offset <= 0) {
                    offset = -7;
                }
                hpcProviderUsageParam.setStartDate(DateUtil.offsetDay(hpcProviderUsageParam.getEndDate(), offset));
            }
        }
        if (DateUtil.compare(hpcProviderUsageParam.getStartDate(), hpcProviderUsageParam.getEndDate()) > 0) {
            return new ArrayList<>();
        }
        List<String> hpcAccountRecordIds = new ArrayList<>();
        List<HpcAccountRecordBo> hpcAccountRecordBos = hpcAccountRecordService.listRecordByProviderType(hpcProviderUsageParam.getHpcProviderType());
        if (CollectionUtil.isNotEmpty(hpcAccountRecordBos)){
            hpcAccountRecordIds.addAll(hpcAccountRecordBos.stream().map(HpcAccountRecordBo::getPid).collect(Collectors.toList()));
        }
        return hpcAccountRecordIds;
    }


}
